import { createMcpHandler } from 'mcp-handler';
import { z } from 'zod';
import { TickTickClient } from '@/lib/ticktick-client';
const getClient = () => {
const token = process.env.TICKTICK_ACCESS_TOKEN;
if (!token) {
throw new Error('TICKTICK_ACCESS_TOKEN is not configured');
}
return new TickTickClient(token);
};
const handler = createMcpHandler(
(server) => {
// Tool: List Projects
server.tool(
'list_projects',
'List all projects in TickTick',
{},
async () => {
try {
const client = getClient();
const projects = await client.listProjects();
return {
content: [{ type: 'text', text: JSON.stringify(projects, null, 2) }],
};
} catch (error) {
return {
content: [{ type: 'text', text: `Error: ${error instanceof Error ? error.message : String(error)}` }],
isError: true,
};
}
}
);
// Tool: Get Tasks
server.tool(
'get_tasks',
'Get tasks from a specific project or all projects',
{
projectId: z.string().optional().describe('Project ID. If not provided, returns tasks from all projects.'),
},
async ({ projectId }) => {
try {
const client = getClient();
if (projectId) {
const projectData = await client.getProjectData(projectId);
return {
content: [{ type: 'text', text: JSON.stringify(projectData, null, 2) }],
};
} else {
const allTasks = await client.getAllTasks();
return {
content: [{ type: 'text', text: JSON.stringify(allTasks, null, 2) }],
};
}
} catch (error) {
return {
content: [{ type: 'text', text: `Error: ${error instanceof Error ? error.message : String(error)}` }],
isError: true,
};
}
}
);
// Tool: Create Task
server.tool(
'create_task',
'Create a new task in TickTick',
{
title: z.string().describe('Title of the task'),
content: z.string().optional().describe('Description of the task'),
projectId: z.string().optional().describe('Project ID (defaults to Inbox)'),
dueDate: z.string().optional().describe('Due date in ISO 8601 format'),
startDate: z.string().optional().describe('Start date in ISO 8601 format'),
isAllDay: z.boolean().optional().describe('Whether this is an all-day task'),
priority: z.number().optional().describe('Priority: 0 (none), 1 (low), 3 (medium), 5 (high)'),
},
async (params) => {
try {
const client = getClient();
const task = await client.createTask(params);
return {
content: [{ type: 'text', text: `Task created:\n${JSON.stringify(task, null, 2)}` }],
};
} catch (error) {
return {
content: [{ type: 'text', text: `Error: ${error instanceof Error ? error.message : String(error)}` }],
isError: true,
};
}
}
);
// Tool: Update Task
server.tool(
'update_task',
'Update an existing task',
{
taskId: z.string().describe('ID of the task to update'),
projectId: z.string().describe('Project ID containing the task'),
title: z.string().optional().describe('New title'),
content: z.string().optional().describe('New description'),
dueDate: z.string().optional().describe('New due date'),
priority: z.number().optional().describe('New priority'),
},
async (params) => {
try {
const client = getClient();
const task = await client.updateTask(params);
return {
content: [{ type: 'text', text: `Task updated:\n${JSON.stringify(task, null, 2)}` }],
};
} catch (error) {
return {
content: [{ type: 'text', text: `Error: ${error instanceof Error ? error.message : String(error)}` }],
isError: true,
};
}
}
);
// Tool: Complete Task
server.tool(
'complete_task',
'Mark a task as completed',
{
taskId: z.string().describe('ID of the task to complete'),
projectId: z.string().describe('Project ID containing the task'),
},
async ({ taskId, projectId }) => {
try {
const client = getClient();
await client.completeTask(projectId, taskId);
return {
content: [{ type: 'text', text: `Task ${taskId} marked as completed.` }],
};
} catch (error) {
return {
content: [{ type: 'text', text: `Error: ${error instanceof Error ? error.message : String(error)}` }],
isError: true,
};
}
}
);
// Tool: Delete Task
server.tool(
'delete_task',
'Delete a task',
{
taskId: z.string().describe('ID of the task to delete'),
projectId: z.string().describe('Project ID containing the task'),
},
async ({ taskId, projectId }) => {
try {
const client = getClient();
await client.deleteTask(projectId, taskId);
return {
content: [{ type: 'text', text: `Task ${taskId} deleted successfully.` }],
};
} catch (error) {
return {
content: [{ type: 'text', text: `Error: ${error instanceof Error ? error.message : String(error)}` }],
isError: true,
};
}
}
);
},
{},
{
redisUrl: process.env.REDIS_URL,
basePath: '/api',
maxDuration: 60,
}
);
export { handler as GET, handler as POST };